/*-------------------------------------------------------------------------
 *
 * partition.c
 *		  与分区相关的数据结构和功能。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *		  src/backend/catalog/partition.c
 *
 *-------------------------------------------------------------------------
*/
#include "postgres.h"

#include "access/attmap.h"
#include "access/genam.h"
#include "access/htup_details.h"
#include "access/sysattr.h"
#include "access/table.h"
#include "catalog/indexing.h"
#include "catalog/partition.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_partitioned_table.h"
#include "nodes/makefuncs.h"
#include "optimizer/optimizer.h"
#include "partitioning/partbounds.h"
#include "rewrite/rewriteManip.h"
#include "utils/fmgroids.h"
#include "utils/partcache.h"
#include "utils/rel.h"
#include "utils/syscache.h"

static Oid	get_partition_parent_worker(Relation fc_inhRel, Oid fc_relid,
										bool *fc_detach_pending);
static void get_partition_ancestors_worker(Relation fc_inhRel, Oid fc_relid,
										   List **fc_ancestors);

/*
 * get_partition_parent
 *		获取给定关系的直接父级
 *
 * 通过扫描pg_inherits返回一个分区的继承父级
 *
 * 如果该分区正在被分离，则会抛出错误，除非even_if_detached为true。
 *
 * 注意：由于此函数假定作为参数传递的OID的关系恰好有一个
 * 父级，因此仅应在已知该关系是分区时调用。
 */
Oid get_partition_parent(Oid fc_relid, bool fc_even_if_detached)
{
	Relation	fc_catalogRelation;
	Oid			fc_result;
	bool		fc_detach_pending;

	fc_catalogRelation = table_open(InheritsRelationId, AccessShareLock);

	fc_result = get_partition_parent_worker(fc_catalogRelation, fc_relid,
										 &fc_detach_pending);

	if (!OidIsValid(fc_result))
		elog(ERROR, "could not find tuple for parent of relation %u", fc_relid);

	if (fc_detach_pending && !fc_even_if_detached)
		elog(ERROR, "relation %u has no parent because it's being detached",
			 fc_relid);

	table_close(fc_catalogRelation, AccessShareLock);

	return fc_result;
}

/*
 * get_partition_parent_worker
 *		扫描pg_inherits关系以返回给定关系的父级OID
 *
 * 如果分区正在分离，则*detach_pending设置为true（但仍然返回
 * 原始父级）。
 */
static Oid get_partition_parent_worker(Relation fc_inhRel, Oid fc_relid, bool *fc_detach_pending)
{
	SysScanDesc fc_scan;
	ScanKeyData fc_key[2];
	Oid			fc_result = InvalidOid;
	HeapTuple	fc_tuple;

	*fc_detach_pending = false;

	ScanKeyInit(&fc_key[0],
				Anum_pg_inherits_inhrelid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_relid));
	ScanKeyInit(&fc_key[1],
				Anum_pg_inherits_inhseqno,
				BTEqualStrategyNumber, F_INT4EQ,
				Int32GetDatum(1));

	fc_scan = systable_beginscan(fc_inhRel, InheritsRelidSeqnoIndexId, true,
							  NULL, 2, fc_key);
	fc_tuple = systable_getnext(fc_scan);
	if (HeapTupleIsValid(fc_tuple))
	{
		Form_pg_inherits fc_form = (Form_pg_inherits) GETSTRUCT(fc_tuple);

		/* 让调用者知道分区正在被分离 */
		if (fc_form->inhdetachpending)
			*fc_detach_pending = true;
		fc_result = fc_form->inhparent;
	}

	systable_endscan(fc_scan);

	return fc_result;
}

/*
 * get_partition_ancestors
 *		获取给定关系的祖先
 *
 * 返回给定关系的祖先列表。
 *
 * 注意：由于此函数假定作为参数传递的OID的关系以及每个祖先恰好
 * 有一个父级，因此仅应在已知该关系是分区时调用。
 */
List * get_partition_ancestors(Oid fc_relid)
{
	List	   *fc_result = NIL;
	Relation	fc_inhRel;

	fc_inhRel = table_open(InheritsRelationId, AccessShareLock);

	get_partition_ancestors_worker(fc_inhRel, fc_relid, &fc_result);

	table_close(fc_inhRel, AccessShareLock);

	return fc_result;
}

/*
 * get_partition_ancestors_worker
 *		get_partition_ancestors的递归助手
 */
static void get_partition_ancestors_worker(Relation fc_inhRel, Oid fc_relid, List **fc_ancestors)
{
	Oid			fc_parentOid;
	bool		fc_detach_pending;

	/*
	 * 递归在最上层结束，即，当没有父级时；还包括
	 * 当分区正在被分离时。
	 */
	fc_parentOid = get_partition_parent_worker(fc_inhRel, fc_relid, &fc_detach_pending);
	if (fc_parentOid == InvalidOid || fc_detach_pending)
		return;

	*fc_ancestors = lappend_oid(*fc_ancestors, fc_parentOid);
	get_partition_ancestors_worker(fc_inhRel, fc_parentOid, fc_ancestors);
}

/*
 * index_get_partition
 *		返回给定分区的索引的OID，该索引是给定索引的子级，
 *		如果没有则返回InvalidOid。
 */
Oid index_get_partition(Relation fc_partition, Oid fc_indexId)
{
	List	   *fc_idxlist = RelationGetIndexList(fc_partition);
	ListCell   *fc_l;

	foreach(fc_l, fc_idxlist)
	{
		Oid			fc_partIdx = lfirst_oid(fc_l);
		HeapTuple	fc_tup;
		Form_pg_class fc_classForm;
		bool		fc_ispartition;

		fc_tup = SearchSysCache1(RELOID, ObjectIdGetDatum(fc_partIdx));
		if (!HeapTupleIsValid(fc_tup))
			elog(ERROR, "cache lookup failed for relation %u", fc_partIdx);
		fc_classForm = (Form_pg_class) GETSTRUCT(fc_tup);
		fc_ispartition = fc_classForm->relispartition;
		ReleaseSysCache(fc_tup);
		if (!fc_ispartition)
			continue;
		if (get_partition_parent(fc_partIdx, false) == fc_indexId)
		{
			list_free(fc_idxlist);
			return fc_partIdx;
		}
	}

	list_free(fc_idxlist);
	return InvalidOid;
}

/*
 * map_partition_varattnos - 将'expr'中所有Vars的varattnos（
 * 具有varno 'fromrel_varno'）从'from_rel'的attnums映射到'to_rel'的
 * attnums，两个都可以是树叶分区或分区表，但必须来自同一分区层次。
 *
 * 我们需要这个，因为尽管所有相同的列名在层次中的所有关系中
 * 必须存在，并且它们也必须具有相同的类型，attnums可能会不同。
 *
 * 注意：这将在任何节点树上工作，因此参数和结果应声明为"Node *"。
 * 但是，绝大多数调用者在处理列表，因此在内部进行转换会更简单。
 */
List * map_partition_varattnos(List *fc_expr, int fc_fromrel_varno,
						Relation fc_to_rel, Relation fc_from_rel)
{
	if (fc_expr != NIL)
	{
		AttrMap    *fc_part_attmap;
		bool		fc_found_whole_row;

		fc_part_attmap = build_attrmap_by_name(RelationGetDescr(fc_to_rel),
											RelationGetDescr(fc_from_rel));
		fc_expr = (List *) map_variable_attnos((Node *) fc_expr,
											fc_fromrel_varno, 0,
											fc_part_attmap,
											RelationGetForm(fc_to_rel)->reltype,
											&fc_found_whole_row);
		/* 由于我们提供了to_rowtype，因此可以忽略found_whole_row。 */
	}

	return fc_expr;
}


/*
 * 检查' attnums' 中是否有任何一个是关系的分区键属性
 *
 * 如果找到了 'attnums' 中的任何一个在某个分区键表达式中被引用，则设置 *used_in_expr。一个列可能被直接使用，也可能作为表达式的一部分；如果发生这种情况，*used_in_expr 可能最终为真或假。这对于当前此函数的使用来说是可以的，因为 *used_in_expr 仅用于定制错误消息文本。
 */
bool has_partition_attrs(Relation fc_rel, Bitmapset *fc_attnums, bool *fc_used_in_expr)
{
	PartitionKey fc_key;
	int			fc_partnatts;
	List	   *fc_partexprs;
	ListCell   *fc_partexprs_item;
	int			fc_i;

	if (fc_attnums == NULL || fc_rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
		return false;

	fc_key = RelationGetPartitionKey(fc_rel);
	fc_partnatts = get_partition_natts(fc_key);
	fc_partexprs = get_partition_exprs(fc_key);

	fc_partexprs_item = list_head(fc_partexprs);
	for (fc_i = 0; fc_i < fc_partnatts; fc_i++)
	{
		AttrNumber	fc_partattno = get_partition_col_attnum(fc_key, fc_i);

		if (fc_partattno != 0)
		{
			if (bms_is_member(fc_partattno - FirstLowInvalidHeapAttributeNumber,
							  fc_attnums))
			{
				if (fc_used_in_expr)
					*fc_used_in_expr = false;
				return true;
			}
		}
		else
		{
			/* 任意表达式 */
			Node	   *fc_expr = (Node *) lfirst(fc_partexprs_item);
			Bitmapset  *fc_expr_attrs = NULL;

			/* 查找所有被引用的属性 */
			pull_varattnos(fc_expr, 1, &fc_expr_attrs);
			fc_partexprs_item = lnext(fc_partexprs, fc_partexprs_item);

			if (bms_overlap(fc_attnums, fc_expr_attrs))
			{
				if (fc_used_in_expr)
					*fc_used_in_expr = true;
				return true;
			}
		}
	}

	return false;
}

/*
 * get_default_partition_oid
 *
 * 给定一个关系 OID，如果存在，则返回默认分区的 OID。尽可能使用 get_default_oid_from_partdesc，以提高效率。
 */
Oid get_default_partition_oid(Oid fc_parentId)
{
	HeapTuple	fc_tuple;
	Oid			fc_defaultPartId = InvalidOid;

	fc_tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(fc_parentId));

	if (HeapTupleIsValid(fc_tuple))
	{
		Form_pg_partitioned_table fc_part_table_form;

		fc_part_table_form = (Form_pg_partitioned_table) GETSTRUCT(fc_tuple);
		fc_defaultPartId = fc_part_table_form->partdefid;
		ReleaseSysCache(fc_tuple);
	}

	return fc_defaultPartId;
}

/*
 * update_default_partition_oid
 *
 * 用新的默认分区 OID 更新 pg_partitioned_table.partdefid。
 */
void update_default_partition_oid(Oid fc_parentId, Oid fc_defaultPartId)
{
	HeapTuple	fc_tuple;
	Relation	pg_partitioned_table;
	Form_pg_partitioned_table fc_part_table_form;

	pg_partitioned_table = table_open(PartitionedRelationId, RowExclusiveLock);

	fc_tuple = SearchSysCacheCopy1(PARTRELID, ObjectIdGetDatum(fc_parentId));

	if (!HeapTupleIsValid(fc_tuple))
		elog(ERROR, "cache lookup failed for partition key of relation %u",
			 fc_parentId);

	fc_part_table_form = (Form_pg_partitioned_table) GETSTRUCT(fc_tuple);
	fc_part_table_form->partdefid = fc_defaultPartId;
	CatalogTupleUpdate(pg_partitioned_table, &fc_tuple->t_self, fc_tuple);

	heap_freetuple(fc_tuple);
	table_close(pg_partitioned_table, RowExclusiveLock);
}

/*
 * get_proposed_default_constraint
 *
 * 此函数返回 new_part_constraints 的否定，这将在添加新_part_constraints 所属的分区后，成为默认分区约束的一个 integral 部分。
 */
List * get_proposed_default_constraint(List *fc_new_part_constraints)
{
	Expr	   *fc_defPartConstraint;

	fc_defPartConstraint = make_ands_explicit(fc_new_part_constraints);

	/*
	 * 通过否定给定的分区约束来派生默认分区的分区约束。分区约束从不评估为 NULL，因此像这样否定是安全的。
	 */
	fc_defPartConstraint = makeBoolExpr(NOT_EXPR,
									 list_make1(fc_defPartConstraint),
									 -1);

	/* 简化，将否定的表达式转换为规范形式 */
	fc_defPartConstraint =
		(Expr *) eval_const_expressions(NULL,
										(Node *) fc_defPartConstraint);
	fc_defPartConstraint = canonicalize_qual(fc_defPartConstraint, true);

	return make_ands_implicit(fc_defPartConstraint);
}
